package fag.core.game;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;

import javax.swing.JPanel;

import fag.core.geometry.Rectangle2D;
import fag.core.geometry.Vector2D;

public class Camera extends JPanel implements Transformable {

	private static final long serialVersionUID = -1453636238210181598L;
	
	Scene scene = null;
	
	private AffineTransform transformation = new AffineTransform();
	
	/**
	 * Constructeur par défault.
	 */
	public Camera() {
		setDoubleBuffered(true);
	}
	
	@Override
	public double getRotation() {
	
		double[] flatmatrix = new double[6];
		
		synchronized(transformation) {
			transformation.getMatrix(flatmatrix);
		}
		
		double cos = flatmatrix[0];
		double sin = flatmatrix[1];
		double acos = Math.acos(cos);
		
		if(sin > 0)
			return acos % (2 * Math.PI);
		else
			return -acos % (2 * Math.PI);
	}
	
	@Override
	public AffineTransform getTransformation() {
		
		synchronized(transformation) {
			return (AffineTransform) transformation.clone();
		}
	}
	
	@Override
	public double getTranslationX() {
		
		synchronized(transformation) {
			return transformation.getTranslateX();
		}
	}

	@Override
	public double getTranslationY() {
		
		synchronized(transformation) {
			return transformation.getTranslateY();
		}
	}
	
	@Override
	public Vector2D getTranslation() {
		
		synchronized(transformation) {
			return new Vector2D(transformation.getTranslateX(), transformation.getTranslateY());
		}
	}
	
	/**
	 * Affichage des objets de la scène.
	 * @param graphics Le contexte graphique.
	 */
	public final void paintComponent(Graphics graphics) {

		Graphics2D graphics2d = (Graphics2D)graphics;
		graphics2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		graphics2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		graphics2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		
		// Redessiner le fond
		graphics2d.clearRect(0, 0, getWidth(), getHeight());
	
		// Transformation de la caméra
		AffineTransform old = graphics2d.getTransform();
		
		synchronized(transformation) {
			graphics2d.transform(transformation);
		}
		
		// Dessiner la scène
		scene.draw(graphics2d);
		
		// Restaurer la scène
		graphics2d.setTransform(old);
		
		drawHud(graphics2d);
	}
	
	@Override
	public Dimension preferredSize() {
		return new Dimension(getWidth(), getHeight());
	}
	
	@Override
	public void rotate(double angle, Referential referential) {
		rotate(angle, 0, 0, referential);
	}

	@Override
	public void rotate(double angle, Vector2D anchor, Referential referential) {
		rotate(angle, anchor.x, anchor.y, referential);
	}

	@Override
	public void rotate(double angle, double x, double y, Referential referential) {
		
		AffineTransform rotation = AffineTransform.getRotateInstance(angle, x, y);
		
		synchronized(transformation) {
			
			switch(referential) {
			case ABSOLUTE:
				transformation.preConcatenate(rotation);
				break;
				
			case RELATIVE:
				transformation.concatenate(rotation);
				break;
			}
		}

		synchronized(this) {
			repaint(0, 0, getWidth(), getHeight());
		}
	}
	
	@Override
	public void scale(double sx, double sy, Referential referential) {
		scale(sx, sy, 0, 0, referential);
	}

	@Override
	public void scale(double sx, double sy, Vector2D anchor, Referential referential) {
		scale(sx, sy, anchor.x, anchor.y, referential);
	}

	@Override
	public void scale(double sx, double sy, double ax, double ay, Referential referential) {

		AffineTransform scaling = AffineTransform.getScaleInstance(sx, sy);
		
		synchronized(transformation) {
			
			switch(referential) {
			case ABSOLUTE:
				transformation.preConcatenate(scaling);
				break;
				
			case RELATIVE:
				transformation.concatenate(scaling);
				break;
			}
		}

		synchronized(this) {
			repaint(0, 0, getWidth(), getHeight());
		}
	}
	
	@Override
	public void setRotation(double angle) {
		
		synchronized(transformation) {
			
			double[] flatmatrix = new double[6];
			transformation.getMatrix(flatmatrix);
			
			double cos = Math.cos(angle);
			double sin = Math.sin(angle);
			
			transformation.setTransform(cos, sin, -sin, cos, flatmatrix[4], flatmatrix[5]);
		}
		
		synchronized(this) {
			repaint(0, 0, getWidth(), getHeight());
		}
	}
	
	@Override
	public void setTransformation(AffineTransform transformation) {
		
		synchronized(transformation) {
			this.transformation = transformation;
		}
		
		synchronized(this) {
			repaint(0, 0, getWidth(), getHeight());
		}
	}
	
	@Override
	public void setTranslation(Vector2D translation) {
		setTranslation(translation.x, translation.y);
	}

	@Override
	public void setTranslation(double x, double y) {
		
		synchronized(transformation) {
		
			double[] flatmatrix = new double[6];
			transformation.getMatrix(flatmatrix);
			
			transformation.setTransform(flatmatrix[0], flatmatrix[1], flatmatrix[2], flatmatrix[3], -x, -y);
		}
		
		synchronized(this) {
			repaint(0, 0, getWidth(), getHeight());
		}
	}

	@Override
	public void translate(Vector2D translation, Referential referential) {
		translate(translation.x, translation.y, referential);
	}

	@Override
	synchronized public void translate(double x, double y, Referential referential) {
		
		AffineTransform translation = AffineTransform.getTranslateInstance(x, y);
		
		synchronized(transformation) {
		
			switch(referential) {
			case ABSOLUTE:
				transformation.preConcatenate(translation);
				break;
				
			case RELATIVE:
				transformation.concatenate(translation);
				break;
			}
		}

		synchronized(this) {
			repaint(0, 0, getWidth(), getHeight());
		}
	}
	
	/**
	 * Ajouter une zone marqué comme à mettre à jour.
	 * @param region La région à mettre à jour.
	 */
	synchronized void addDirtyRegion(Rectangle2D region) {
		
		Rectangle2D dirt = null;
		synchronized(transformation) {
			dirt = region.transform(transformation).getBounds();
		}
		
		repaint((int)dirt.getX(), (int)dirt.getY(), (int)dirt.getWidth() + 1, (int)dirt.getHeight() + 1);
	}
	
	/**
	 * Affichage du HUD.
	 * Point d'extension du framework.
	 * @param graphics2d Le contexte de dessin.
	 */
	protected void drawHud(Graphics2D graphics2d) {
	}
}
